﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

namespace BrightSword.OfficeInterop.Internal
{
    ///<summary>
    /// This is a set of utility extension methods for Excel Interop.
    ///</summary>
    /// <remarks>
    /// This code is made available for open-source use at http://excelreader.codeplex.com/
    /// </remarks>
    /// <see cref="http://excelreader.codeplex.com/"/>
    internal static class ExcelExtensions
    {
        /// <summary>
        /// Returns true if there are no values in any of the cells in this row
        /// </summary>
        /// <param name="worksheet">The worksheet to look in</param>
        /// <param name="rowIndex">The value specifying the row to check</param>
        /// <param name="maxColumnCount">Optionally, the maximum number of columns to check</param>
        /// <returns>True if the entire row is blank. False otherwise</returns>
        public static bool IsBlank(this Worksheet worksheet, int rowIndex, int maxColumnCount = int.MaxValue)
        {
            var rows = worksheet.Rows;

            try
            {
                var row = rows.Rows[rowIndex] as Range;
                if (row == null)
                {
                    return true;
                }

                try
                {
                    return row.IsBlank(maxColumnCount);
                }
                finally
                {
                    Marshal.ReleaseComObject(row);
                }
            }
            finally
            {
                Marshal.ReleaseComObject(rows);
            }
        }

        /// <summary>
        /// Returns true if there are no values in any of the cells in this row
        /// </summary>
        /// <param name="row">The row to check</param>
        /// <param name="maxColumnCount">optionally, the limit of the number of columns to check</param>
        /// <returns>True if the entire row is blank. False otherwise</returns>
        public static bool IsBlank(this Range row, int maxColumnCount = int.MaxValue)
        {
            maxColumnCount = Math.Min(row.Columns.Count, maxColumnCount);

            for (var columnIndex = 1; columnIndex < maxColumnCount; columnIndex++)
            {
                var cell = row.Cells[1, columnIndex] as Range;
                if (cell == null)
                {
                    continue;
                }

                try
                {
                    if (!(string.IsNullOrEmpty(cell.Text as string)))
                    {
                        return false;
                    }
                }
                finally
                {
                    Marshal.ReleaseComObject(cell);
                }
            }

            return true;
        }	
	
        public static void ProcessWorksheet(this Worksheet worksheet, Action<Range, object [ ]> rowProcessor, params object [ ] args)
        {
            var rows = worksheet.Rows;

            try
            {
                for (var rowIndex = 1; rowIndex < rows.Rows.Count; rowIndex++)
                {
                    var row = rows.Rows[rowIndex] as Range;
                    try
                    {
                        if (IsBlank(row))
                        {
                            break;
                        }
                        rowProcessor(row, args);
                    }
                    finally
                    {
                        Marshal.ReleaseComObject(row);
                    }
                }
            }
            finally
            {
                Marshal.ReleaseComObject(rows);
            }
        }

        public static void ProcessWorkbook(
            this Workbook workbook, Action<Range, object [ ]> rowProcessor, String sheetName = null, params object [ ] args)
        {
            var worksheets = workbook.Sheets.Cast<Worksheet>();

            worksheets = String.IsNullOrEmpty(sheetName)
                ? worksheets
                : worksheets.Where(_sheet => String.Compare(_sheet.Name, sheetName, true) == 0);

            try
            {
                foreach (var worksheet in worksheets)
                {
                    try
                    {
                        ProcessWorksheet(worksheet, rowProcessor, args);
                    }
                    finally
                    {
                        Marshal.ReleaseComObject(worksheet);
                    }
                }
            }
            finally
            {
                Marshal.ReleaseComObject(worksheets);
            }
        }

        public static IEnumerable<TResult> Map<TResult>(
            this Workbook workbook, Func<Range, Worksheet, object [ ], TResult> rowProcessor, String sheetName = null, params object [ ] args)
        {
            var worksheets = workbook.Sheets.Cast<Worksheet>();

            worksheets = String.IsNullOrEmpty(sheetName)
                ? worksheets
                : worksheets.Where(_sheet => String.Compare(_sheet.Name, sheetName, true) == 0);

            try
            {
                foreach (var worksheet in worksheets)
                {
                    try
                    {
                        var rows = worksheet.Rows;

                        try
                        {
                            for (var rowIndex = 1; rowIndex < rows.Rows.Count; rowIndex++)
                            {
                                var row = rows.Rows[rowIndex] as Range;
                                try
                                {
                                    if (IsBlank(row))
                                    {
                                        yield break;
                                    }

                                    yield return rowProcessor(row, worksheet, args);
                                }
                                finally
                                {
                                    Marshal.ReleaseComObject(row);
                                }
                            }
                        }
                        finally
                        {
                            Marshal.ReleaseComObject(rows);
                        }
                    }
                    finally
                    {
                        Marshal.ReleaseComObject(worksheet);
                    }
                }
            }
            finally
            {
                Marshal.ReleaseComObject(worksheets);
            }
        }

        public static void ProcessWorkbook(this Workbook workbook, IExcelReadProcessorArg readProcessor, params object [ ] args)
        {
            try
            {
                if ((readProcessor.BeforeProcessWorkbook != null) && (!readProcessor.BeforeProcessWorkbook(workbook, args)))
                {
                    return;
                }

                if (readProcessor.ProcessWorkbook != null)
                {
                    readProcessor.ProcessWorkbook(workbook, args);
                    return;
                }

                var worksheets = workbook.Sheets;
                try
                {
                    foreach (Worksheet worksheet in worksheets)
                    {
                        try
                        {
                            try
                            {
                                if ((readProcessor.BeforeProcessWorksheet != null) && (!readProcessor.BeforeProcessWorksheet(worksheet, args)))
                                {
                                    return;
                                }

                                if (readProcessor.ProcessWorksheet != null)
                                {
                                    readProcessor.ProcessWorksheet(worksheet, args);
                                }
                                else
                                {
                                    var rows = worksheet.Rows;

                                    try
                                    {
                                        for (var rowIndex = 1; rowIndex < rows.Rows.Count; rowIndex++)
                                        {
                                            var row = rows.Rows[rowIndex] as Range;
                                            try
                                            {
                                                if (IsBlank(row))
                                                {
                                                    break;
                                                }
                                                try
                                                {
                                                    if ((readProcessor.BeforeProcessRow != null) && (!readProcessor.BeforeProcessRow(row, args)))
                                                    {
                                                        return;
                                                    }

                                                    if (readProcessor.ProcessRow != null)
                                                    {
                                                        readProcessor.ProcessRow(row, args);
                                                    }
                                                    // no default row processing
                                                    //else
                                                    //{ }
                                                }
                                                finally
                                                {
                                                    if (readProcessor.AfterProcessRow != null)
                                                    {
                                                        readProcessor.AfterProcessRow(row, args);
                                                    }
                                                }
                                            }
                                            finally
                                            {
                                                Marshal.ReleaseComObject(row);
                                            }
                                        }
                                    }
                                    finally
                                    {
                                        Marshal.ReleaseComObject(rows);
                                    }
                                }
                            }
                            finally
                            {
                                if (readProcessor.AfterProcessWorksheet != null)
                                {
                                    readProcessor.AfterProcessWorksheet(worksheet, args);
                                }
                            }
                        }
                        finally
                        {
                            Marshal.ReleaseComObject(worksheet);
                        }
                    }
                }
                finally
                {
                    Marshal.ReleaseComObject(worksheets);
                }
            }
            finally
            {
                if (readProcessor.AfterProcessWorkbook != null)
                {
                    readProcessor.AfterProcessWorkbook(workbook, args);
                }
            }
        }
    }
}